﻿namespace EnergyGeneratorSimVisualizer
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Threading;
    using EnergyGeneratorSim;

    public class MainWindowViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private IAsyncResult cancelToken;
        private bool isCalculating;
        private IList<string> loggingOutput = new ObservableCollection<string>();
        private SynchronizationContext context = SynchronizationContext.Current;
        private double progressReportMaximum;
        private double progressReportCurrentValue;

        public MainWindowViewModel()
        {
            this.ProcessedData = new ObservableCollection<SimulationDataItem>();
            this.ProgressReportCurrentValue = 0;
            this.ProgressReportMaximum = 100;
        }

        protected void OnPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public void CalculateSimulation()
        {
            this.IsCalculating = true;
            this.loggingOutput.Clear();

            this.ProcessedData = new ObservableCollection<SimulationDataItem>();
            this.OnPropertyChanged("ProcessedData");

            var job = new ThreadStart(this.CalculateSimulationWork);

            cancelToken = job.BeginInvoke(this.CalculateSimulationReport, null);
        }

        private void CalculateSimulationWork()
        {
            var simulator = new RaumenergiemotorDFEM();
            simulator.LogCallback = this.Log;
            simulator.ProgressReportStepCount = 50;
            simulator.InitializeProgressReport = this.InitializeProgressReport;
            simulator.ProgressReport = this.ProgressReport;
            var result = simulator.Calculate();

            this.Data = result.SimulationData;

            var processedData = new List<SimulationDataItem>(1000);

            int stepWidth = result.SimulationData.Length / 1000;

            for (int i = 0; i < 1000; i++)
            {
                processedData.Add(result.SimulationData[i * stepWidth]);
            }

            this.ProcessedData = processedData;
        }

        private void CalculateSimulationReport(IAsyncResult data)
        {
            this.OnPropertyChanged("Data");
            this.OnPropertyChanged("ProcessedData");
            this.IsCalculating = false;
        }

        public IEnumerable<SimulationDataItem> Data { get; private set; }

        public ICollection<SimulationDataItem> ProcessedData { get; private set; }

        public bool IsNotCalculating
        {
            get
            {
                return !this.isCalculating;
            }
        }

        public bool IsCalculating
        {
            get
            {
                return this.isCalculating;
            }

            set
            {
                this.isCalculating = value;
                this.OnPropertyChanged("IsCalculating");
                this.OnPropertyChanged("IsNotCalculating");
            }
        }

        public double ProgressReportMaximum
        {
            get
            {
                return this.progressReportMaximum;
            }

            set
            {
                this.progressReportMaximum = value;
                this.OnPropertyChanged("ProgressReportMaximum");
            }
        }

        public double ProgressReportCurrentValue
        {
            get
            {
                return this.progressReportCurrentValue;
            }

            set
            {
                this.progressReportCurrentValue = value;
                this.OnPropertyChanged("ProgressReportCurrentValue");
            }
        }

        public IEnumerable<string> LoggingOutput
        {
            get
            {
                return this.loggingOutput;
            }
        }

        private void Log(string message)
        {
            if (this.context == null)
            {
                this.LogInternal(message);
            }

            this.context.Send(this.LogInternal, message);
        }

        private void LogInternal(object data)
        {
            string message = data.ToString();

            string[] lines = message.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);

            if (lines.Length == 0)
            {
                return;
            }

            // first line is appended to the current line
            var currentLine = string.Empty;

            if (this.loggingOutput.Count > 0)
            {
                currentLine = this.loggingOutput[this.loggingOutput.Count - 1];
                this.loggingOutput.RemoveAt(this.loggingOutput.Count - 1);
            }

            currentLine += lines[0];
            this.loggingOutput.Add(currentLine);

            // add the other lines
            for (int i = 1; i < lines.Length; i++)
            {
                this.loggingOutput.Add(lines[i]);
            }
        }

        private void InitializeProgressReport(ulong stepCount)
        {
            if (this.context == null)
            {
                this.InitializeProgressReportInternal(stepCount);
            }

            this.context.Send(this.InitializeProgressReportInternal, stepCount);
        }

        private void InitializeProgressReportInternal(object data)
        {
            ulong stepCount = (ulong)data;

            this.ProgressReportMaximum = stepCount;
            this.ProgressReportCurrentValue = 0;
        }

        private void ProgressReport(ulong currentValue, SimulationDataItem data)
        {
            if (this.context == null)
            {
                this.ProgressReportInternal(currentValue);
            }

            this.context.Send(this.ProgressReportInternal, new KeyValuePair<ulong, SimulationDataItem>(currentValue, data));
        }

        private void ProgressReportInternal(object data)
        {
            var item = (KeyValuePair<ulong, SimulationDataItem>)data;

            this.ProgressReportCurrentValue = item.Key;
            this.ProcessedData.Add(item.Value);
        }

    }
}
